/*
 * Unidata Platform
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *
 * Commercial License
 * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *
 * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 * For clarification or additional options, please contact: info@unidata-platform.com
 * -------
 * Disclaimer:
 * -------
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 */
package org.unidata.mdm.rest.meta.converter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.CollectionUtils;
import org.unidata.mdm.core.type.security.Right;
import org.unidata.mdm.core.util.SecurityUtils;
import org.unidata.mdm.meta.type.model.ArrayValueType;
import org.unidata.mdm.meta.type.model.MetaModelAttribute;
import org.unidata.mdm.meta.type.model.attributes.ArrayMetaModelAttribute;
import org.unidata.mdm.meta.type.model.attributes.ComplexMetaModelAttribute;
import org.unidata.mdm.meta.type.model.attributes.SimpleMetaModelAttribute;
import org.unidata.mdm.meta.type.model.entities.AbstractEntity;
import org.unidata.mdm.meta.type.model.entities.NestedEntity;
import org.unidata.mdm.rest.core.converter.RoleRoConverter;
import org.unidata.mdm.rest.meta.ro.AbstractAttributeRO;
import org.unidata.mdm.rest.meta.ro.AbstractEntityRO;
import org.unidata.mdm.rest.meta.ro.ArrayAttributeDefinitionRO;
import org.unidata.mdm.rest.meta.ro.ArrayDataType;
import org.unidata.mdm.rest.meta.ro.ComplexAttributeRO;
import org.unidata.mdm.rest.meta.ro.NestedEntityRO;
import org.unidata.mdm.rest.meta.ro.SimpleAttributeRO;
import org.unidata.mdm.rest.system.ro.SimpleDataType;

/**
 * @author Mikhail Mikhailov
 * Abstract entity converter.
 */
public abstract class AbstractEntityDefinitionConverter {

    /**
     * Constructor.
     */
    protected AbstractEntityDefinitionConverter() {
        super();
    }

    /**
     * Copy abstract entity data from internal to REST.
     *
     * @param source
     *            internal
     * @param target
     *            REST
     */
    public static void toAbstractEntityData(AbstractEntity<?> source, AbstractEntityRO target) {
        target.setName(source.getName());
        target.setDisplayName(source.getDisplayName());
        target.setDescription(source.getDescription());
        target.setCustomProperties(CustomPropertiesConverter.to(source.getCustomProperties()));
    }

    /**
     * Copy abstract attribute data from internal to REST.
     *
     * @param source
     *            internal
     * @param target
     *            REST
     */
    public static void toAbstractAttributeData(MetaModelAttribute source, AbstractAttributeRO target, String securityPath) {

        target.setName(source.getName());
        target.setDisplayName(source.getDisplayName());
        target.setDescription(source.getDescription());

        // 1. Check for data admin object first
        Right adminRights = SecurityUtils.getRightsForResource(SecurityUtils.ADMIN_DATA_MANAGEMENT_RESOURCE_NAME);
        Right resourceRights = SecurityUtils.getRightsForResource(String.join(".", securityPath, source.getName()));

        target.setRights(RoleRoConverter.convertRightDTO(resourceRights));
        if (SecurityUtils.isAdminUser() || adminRights != null || resourceRights == null) {
            target.setHidden(source.isHidden());
            target.setReadOnly(source.isReadOnly());
        } else {
            target.setHidden(!resourceRights.isRead());
            target.setReadOnly(!resourceRights.isCreate() || !resourceRights.isDelete() || !resourceRights.isUpdate());
        }

        target.setCustomProperties(CustomPropertiesConverter.to(source.getCustomProperties()));
    }

    /**
     * Copies simple attributes from list to list.
     *
     * @param source
     *            the source
     */
    public static List<SimpleAttributeRO> toSimpleAttrs(List<SimpleMetaModelAttribute> source, String securityPath) {

        if (CollectionUtils.isEmpty(source)) {
            return Collections.emptyList();
        }

        List<SimpleAttributeRO> target = new ArrayList<>();
        for (SimpleMetaModelAttribute sourceAttr : source) {
            target.add(to(sourceAttr, securityPath));
        }

        return target;
    }

    /**
     * Copy simple attributes data from REST to internal.
     *
     * @param source
     *            REST source
     */
    public static SimpleAttributeRO to(SimpleMetaModelAttribute source, String securityPath) {

        SimpleAttributeRO target =  new SimpleAttributeRO();

        toAbstractAttributeData(source, target, securityPath);

        target.setEnumDataType(source.getEnumDataType());
        target.setLookupEntityType(source.getLookupEntityType());
        target.setLookupEntityCodeAttributeType(getSimpleDataType(source.getLookupEntityCodeAttributeType()));
        target.setLookupEntityDisplayAttributes(new ArrayList<>(source.getLookupEntityDisplayAttributes()));
        target.setLookupEntitySearchAttributes(new ArrayList<>(source.getLookupEntitySearchAttributes()));
        target.setDictionaryDataType(String.join("=>>", source.getDictionaryDataType()));
        target.setLinkDataType(source.getLinkDataType());
        target.setNullable(source.isNullable());
        target.setSimpleDataType(getSimpleDataType(source.getSimpleDataType()));
        target.setUseAttributeNameForDisplay(source.isUseAttributeNameForDisplay());

        target.setUnique(source.isUnique());
        target.setOrder(source.getOrder());

        target.setSearchable(source.isSearchable());
        target.setSearchMorphologically(source.isSearchMorphologically());
        target.setSearchCaseInsensitive(source.isSearchCaseInsensitive());
        target.setDisplayable(source.isDisplayable());
        target.setMainDisplayable(source.isMainDisplayable());
        if(source.getMeasureSettings() != null){
            target.setDefaultUnitId(source.getMeasureSettings().getDefaultUnitId());
            target.setValueId(source.getMeasureSettings().getCategoryId());
        }

        return target;
    }

    private static SimpleDataType getSimpleDataType(org.unidata.mdm.meta.type.model.SimpleDataType innerType) {
        if (innerType == null) {
            return null;
        }
        if (innerType == org.unidata.mdm.meta.type.model.SimpleDataType.MEASURED) {
            return SimpleDataType.NUMBER;
        } else {
            return SimpleDataType.fromValue(innerType.value());
        }
    }

    /**
     * Copies simple attributes from list to list.
     *
     * @param source
     *            the source
     */
    public static List<ArrayAttributeDefinitionRO> toArrayAttrs(List<ArrayMetaModelAttribute> source, String securityPath) {

        if (CollectionUtils.isEmpty(source)) {
            return Collections.emptyList();
        }

        List<ArrayAttributeDefinitionRO> target = new ArrayList<>();
        for (ArrayMetaModelAttribute sourceAttr : source) {
            target.add(to(sourceAttr, securityPath));
        }

        return target;
    }

    /**
     * Copy simple attributes data from REST to internal.
     *
     * @param source
     *            REST source
     */
    public static ArrayAttributeDefinitionRO to(ArrayMetaModelAttribute source, String securityPath) {

        ArrayAttributeDefinitionRO target =  new ArrayAttributeDefinitionRO();

        toAbstractAttributeData(source, target, securityPath);

        target.setNullable(source.isNullable());
        target.setArrayDataType(getArrayValueType(source.getArrayValueType()));

        target.setOrder(source.getOrder());

        target.setSearchable(source.isSearchable());
        target.setDisplayable(source.isDisplayable());
        target.setMainDisplayable(source.isMainDisplayable());
        target.setSearchMorphologically(source.isSearchMorphologically());
        target.setSearchCaseInsensitive(source.isSearchCaseInsensitive());
        target.setLookupEntityType(source.getLookupEntityType());
        target.setLookupEntityCodeAttributeType(getArrayValueType(source.getLookupEntityCodeAttributeType()));
        target.setLookupEntityDisplayAttributes(new ArrayList<>(source.getLookupEntityDisplayAttributes()));
        target.setLookupEntitySearchAttributes(new ArrayList<>(source.getLookupEntitySearchAttributes()));
        target.setDictionaryDataType(String.join("=>>", source.getDictionaryDataType()));
        target.setExchangeSeparator(source.getExchangeSeparator());
        target.setUseAttributeNameForDisplay(source.isUseAttributeNameForDisplay());

        return target;
    }

    private static ArrayDataType getArrayValueType(ArrayValueType innerType) {

        if (innerType == null) {
            return null;
        }

        return ArrayDataType.fromValue(innerType.value().value());
    }

    /**
     * Copy list of internal complex attributes to REST target
     * @param source internal
     * @param refs the references
     */
    public static List<ComplexAttributeRO> to(
            List<ComplexMetaModelAttribute> source,
            List<NestedEntity> refs,
            String securityPath
    ) {

        if (CollectionUtils.isEmpty(source)) {
            return Collections.emptyList();
        }

        List<ComplexAttributeRO> target = new ArrayList<>();
        for (ComplexMetaModelAttribute attr : source) {
            target.add(to(attr, refs, securityPath));
        }

        return target;
    }

    /**
     * Convert complex attributes.
     * @param source internal
     * @param refs the model
     */
    public static ComplexAttributeRO to(ComplexMetaModelAttribute source, List<NestedEntity> refs,
                                                String securityPath) {

        ComplexAttributeRO result = new ComplexAttributeRO();

        toAbstractAttributeData(source, result, securityPath);

        if (source.getMinCount() != null) {
            result.setMinCount(source.getMinCount());
        }
        if (source.getMaxCount() != null) {
            result.setMaxCount(source.getMaxCount());
        }

        result.setOrder(source.getOrder());
        result.setNestedEntityKeyAttribute(source.getNestedEntityKeyAttribute());
        result.setNestedEntityName(source.getNestedEntityName());

        return result;
    }

    /**
     * Copy nested entity internal to REST.
     *
     * @param source
     *            internal
     * @param refs
     *            the references
     * @return REST
     */
    public static NestedEntityRO to(NestedEntity source, List<NestedEntity> refs, String securityPath) {

        NestedEntityRO result = new NestedEntityRO();

        toAbstractEntityData(source, result);

        result.getSimpleAttributes().addAll(toSimpleAttrs(source.getSimpleAttribute(), securityPath));
        result.getArrayAttributes().addAll(toArrayAttrs(source.getArrayAttribute(), securityPath));
        result.getComplexAttributes().addAll(to(source.getComplexAttribute(), refs, securityPath));

        return result;
    }

    /**
     * Converts complex attributes from REST to intenal.
     *
     * @param source            REST type
     * @param targetList the target list
     * @param nestedEntities the model
     */
    public static void fromComplexAttribute(
            ComplexAttributeRO source,
            List<ComplexMetaModelAttribute> targetList,
            Map<String, NestedEntity> nestedEntities
    ) {

        ComplexMetaModelAttribute result = new ComplexMetaModelAttribute();

        fromAbstractAttributeData(source, result);

        if (source.getMinCount() != null) {
            result.setMinCount(source.getMinCount());
        }
        if (source.getMaxCount() != null) {
            result.setMaxCount(source.getMaxCount());
        }

        result.setOrder(source.getOrder());
        result.setNestedEntityKeyAttribute(source.getNestedEntityKeyAttribute());
        result.setNestedEntityName(source.getNestedEntityName());

        targetList.add(result);
    }

    /**
     * Converts nested entity from REST to internal.
     *
     * @param source REST
     * @param nestedEntities model
     * @return internal
     */
    public static NestedEntity fromNestedEntity(NestedEntityRO source, Map<String, NestedEntity> nestedEntities) {
        NestedEntity result = new NestedEntity();

        fromAbstractEntityData(source, result);
        SimpleAttributeDefConverter.copySimpleAttributeDataList(source.getSimpleAttributes(), result.getSimpleAttribute());
        ArrayAttributeDefConverter.copySimpleAttributeDataList(source.getArrayAttributes(), result.getArrayAttribute());
        fromComplexAttributeDataList(source.getComplexAttributes(), result.getComplexAttribute(), nestedEntities);

        return result;
    }

    /**
     * Copy abstract entity definition from REST to internal.
     *
     * @param source
     *            REST source
     * @param target
     *            internal
     */
    public static void fromAbstractEntityData(AbstractEntityRO source, AbstractEntity<?> target) {
        target.setName(source.getName());
        target.setDisplayName(source.getDisplayName());
        target.setDescription(source.getDescription());
        target.withCustomProperties(CustomPropertiesConverter.from(source.getCustomProperties()));
    }

    /**
     * Copy abstract attribute data from REST to internal.
     *
     * @param source
     *            REST source
     * @param target
     *            internal
     */
    public static void fromAbstractAttributeData(AbstractAttributeRO source, MetaModelAttribute target) {
        target.setName(source.getName());
        target.setDisplayName(source.getDisplayName());
        target.setDescription(source.getDescription());
        target.setHidden(source.isHidden());
        target.setReadOnly(source.isReadOnly());
        target.setCustomProperties(new ArrayList<>(CustomPropertiesConverter.from(source.getCustomProperties())));
    }

    /**
     * Copy list of REST complex attributes to internal target.
     *
     * @param source internal
     * @param target REST
     * @param nestedEntities the model
     */
    public static void fromComplexAttributeDataList(
            List<ComplexAttributeRO> source,
            List<ComplexMetaModelAttribute> target,
            Map<String, NestedEntity> nestedEntities) {

        if (source == null) {
            return;
        }

        for (ComplexAttributeRO attr : source) {
            fromComplexAttribute(attr, target, nestedEntities);
        }
    }
}
